import { BeanDefine, BeanPostProcessor, Component, springLog } from 'j-spring';
import {
  Service,
  Transactional,
  ServiceAnnoParam,
  TranscationParam,
} from './SpringJpaAnnotation';
import { SpringTxManager } from './SpringTxManager';

export const springTxManager = new SpringTxManager();

/**
 * 获取属性名集合，递归获取继承的属性名
 */
function getPropertyNameFromProtype(bean: any): string[] {
  let ps: string[] = [];
  const loadProperty = (b: any) => {
    if (b && b instanceof Object) {
      const keys = Reflect.ownKeys(b);
      ps = ps.concat(keys as any);
      loadProperty(b.__proto__);
    }
  };
  loadProperty(bean.constructor.prototype);
  return ps.filter(v => v !== 'constructor');
}

/**
 * 事务方法代理
 */
function transactionMethodProxy(
  method: Function,
  transcationParam?: TranscationParam
): Function {
  return new Proxy(method, {
    /**
     * 代理的必须是异步方法！ 否则跑出异常信息！
     * target
     * oriMethod 原方法
     * obj 调用该方法的对象
     * args 用户传入的参数
     */
    async apply(oriMethod, obj, args) {
      /**
       * 实际运算的代理方法
       */
      const runFn = async () => {
        //一律视为异步方法
        const result = oriMethod.apply(obj, args);

        //代理的方法必须是异步方法 同步的没有意义 无需代理
        if (!(result instanceof Promise)) {
          const msg = `${msgPrefix}:该方法未返回promise对象!请检查`;
          springLog.error(msg);
          throw msg;
        }

        const finalResult = await result;
        return finalResult;
      };

      const msgPrefix = `${obj.constructor?.name}:${oriMethod?.name}`;

      springLog.verbose(`${msgPrefix} => 事务代理AOP切入 `);

      /**
       * 判断沿用前置事务还是开启新的事务
       */
      const existTxId = springTxManager.txLocalStorage.getStore();

      if (existTxId !== undefined) {
        if (transcationParam?.isNewTx) {
          springLog.verbose(
            `${msgPrefix} => isNewTx=${transcationParam?.isNewTx},开启新事物`
          );
        } else {
          springLog.verbose(`${msgPrefix}:沿用前置事务id:${existTxId}`);
          return await runFn();
        }
      }

      /***
       * 开启新事物 处理好事务的生命周期
       */
      const txId = springTxManager.registerEntityManager();

      springLog.verbose(`${msgPrefix} => 注册事务ID:${txId} `);

      const fResult = await springTxManager.txLocalStorage.run(
        txId,
        async () => {
          let result;
          try {
            await springTxManager.startTx(txId);
            result = await runFn();
            await springTxManager.cmmitTx(txId);
          } catch (e) {
            springLog.error(`${msgPrefix} => 出错:${e},事务回滚`);
            await springTxManager.rollbackTx(txId); //回滚后抛出异常
            console.error(e);
            throw e;
          } finally {
            springTxManager.release(txId);
            springLog.verbose(`${msgPrefix} => 事务代理AOP结束  `);
          }

          return result;
        }
      );

      return fResult;
    },
  });
}

@Component()
export class SpringTransictionalPostProcessor implements BeanPostProcessor {
  getSort(): number {
    return 200;
  }
  postProcessBeforeInitialization(bean: any, beanDefine: BeanDefine): Object {
    if (beanDefine.hasAnnotation(Service)) {
      //检测该类是否开启全局事务
      const serviceParam = beanDefine.getAnnotation(Service)
        ?.params as ServiceAnnoParam;

      for (let p of getPropertyNameFromProtype(bean)) {
        //检测该方法开启了事务
        const methodDefine = beanDefine.methodList.find(
          md => md.name === p && md.hasAnnotation(Transactional)
        );

        const isMethodOpenTx = !!methodDefine;

        const transcationParam = methodDefine?.getAnnotation(Transactional)
          ?.params as TranscationParam;

        if (serviceParam?.allMethodProxy || isMethodOpenTx)
          bean[p] = transactionMethodProxy(bean[p], transcationParam);
      }

      return bean;
    }
    return bean;
  }
  postProcessAfterInitialization(bean: any, _beanDefine: BeanDefine): Object {
    return bean;
  }
}
